#pragma once

#include "vec3.h"

OPEN_JLIB_NS


struct alignas(16) Vec4 {

  f32 p[4];

  enum class BlendFunc {
    Add, Sub, SubR, Mul, Min, Max,
  };

  enum class BlendParam {
    Zero, One,
    SrcAlpha, DstAlpha,
    OneMinusSrcAlpha, OneMinusDstAlpha,
    SrcColor, DstColor,
    OneMinusSrcColor, OneMinusDstColor,
  };


  inline Vec4 () noexcept = default;
  inline Vec4 (f32 x, f32 y, f32 z, f32 w) noexcept {
    p[0] = x;
    p[1] = y;
    p[2] = z;
    p[3] = w;
  }
  inline Vec4 (Vec3 v, f32 w = 0.0F) noexcept {
    p[0] = v.p[0];
    p[1] = v.p[1];
    p[2] = v.p[2];
    p[3] = w;
  }
  inline Vec4 (f32 s) noexcept {
    p[0] = s;
    p[1] = s;
    p[2] = s;
    p[3] = s;
  }
  inline Vec4 (const f32* m) noexcept {
    p[0] = m[0];
    p[1] = m[1];
    p[2] = m[2];
    p[3] = m[3];
  }


  inline operator Vec3 () const noexcept {
    return {p[0], p[1], p[2]};
  }


  inline f32 operator[] (uwl i) const noexcept {
    return p[i];
  }


  inline f32 x () const noexcept {
    return p[0];
  }
  inline f32 y () const noexcept {
    return p[1];
  }
  inline f32 z () const noexcept {
    return p[2];
  }
  inline f32 w () const noexcept {
    return p[3];
  }


  inline Vec4& x (f32 s) noexcept {
    p[0] = s;
    return *this;
  }
  inline Vec4& y (f32 s) noexcept {
    p[1] = s;
    return *this;
  }
  inline Vec4& z (f32 s) noexcept {
    p[2] = s;
    return *this;
  }
  inline Vec4& w (f32 s) noexcept {
    p[3] = s;
    return *this;
  }


  inline Vec4& operator+= (Vec4 v) noexcept {
    p[0] += v.p[0];
    p[1] += v.p[1];
    p[2] += v.p[2];
    p[3] += v.p[3];
    return *this;
  }
  inline Vec4& operator-= (Vec4 v) noexcept {
    p[0] -= v.p[0];
    p[1] -= v.p[1];
    p[2] -= v.p[2];
    p[3] -= v.p[3];
    return *this;
  }
  inline Vec4& operator*= (Vec4 v) noexcept {
    p[0] *= v.p[0];
    p[1] *= v.p[1];
    p[2] *= v.p[2];
    p[3] *= v.p[3];
    return *this;
  }
  inline Vec4& operator/= (Vec4 v) noexcept {
    p[0] /= v.p[0];
    p[1] /= v.p[1];
    p[2] /= v.p[2];
    p[3] /= v.p[3];
    return *this;
  }


  inline Vec4& operator*= (f32 s) noexcept {
    p[0] *= s;
    p[1] *= s;
    p[2] *= s;
    p[3] *= s;
    return *this;
  }
  inline Vec4& operator/= (f32 s) noexcept {
    p[0] /= s;
    p[1] /= s;
    p[2] /= s;
    p[3] /= s;
    return *this;
  }


  inline Vec4& clamp (Vec4 min, Vec4 max) noexcept {
    p[0] = fminf(fmaxf(p[0], min.p[0]), max.p[0]);
    p[1] = fminf(fmaxf(p[1], min.p[1]), max.p[1]);
    p[2] = fminf(fmaxf(p[2], min.p[2]), max.p[2]);
    p[3] = fminf(fmaxf(p[3], min.p[3]), max.p[3]);
    return *this;
  }


  inline Vec4 operator+ (Vec4 v) const noexcept {
    return {p[0]+v.p[0], p[1]+v.p[1], p[2]+v.p[2], p[3]+v.p[3]};
  }
  inline Vec4 operator- (Vec4 v) const noexcept {
    return {p[0]-v.p[0], p[1]-v.p[1], p[2]-v.p[2], p[3]-v.p[3]};
  }
  inline Vec4 operator* (Vec4 v) const noexcept {
    return {p[0]*v.p[0], p[1]*v.p[1], p[2]*v.p[2], p[3]*v.p[3]};
  }
  inline Vec4 operator/ (Vec4 v) const noexcept {
    return {p[0]/v.p[0], p[1]/v.p[1], p[2]/v.p[2], p[3]/v.p[3]};
  }


  inline Vec4 operator* (f32 s) const noexcept {
    return {p[0]*s, p[1]*s, p[2]*s, p[3]*s};
  }
  inline Vec4 operator/ (f32 s) const noexcept {
    return {p[0]/s, p[1]/s, p[2]/s, p[3]/s};
  }


  inline Vec4 operator+ () const noexcept {
    return {fabsf(p[0]), fabsf(p[1]), fabsf(p[2]), fabsf(p[3])};
  }
  inline Vec4 operator- () const noexcept {
    return {-p[0], -p[1], -p[2], -p[3]};
  }


  inline Vec4 alpha (f32 alpha) const noexcept {
    return {p[0], p[1], p[2], alpha};
  }
  inline Vec4 luminance () const noexcept {
    auto l = 0.299F*p[0] + 0.587F*p[1] + 0.114F*p[2];
    return {l, l, l, p[3]};
  }


  inline static Vec4 zero () noexcept {
    return {0.0F, 0.0F, 0.0F, 0.0F};
  }
  inline static Vec4 one () noexcept {
    return {1.0F, 1.0F, 1.0F, 1.0F};
  }
  template<u8 Com>
  inline static Vec4 unit () noexcept;


  inline static Vec4 min (Vec4 a, Vec4 b) noexcept {
    return {
      fminf(a.p[0], b.p[0]),
      fminf(a.p[1], b.p[1]),
      fminf(a.p[2], b.p[2]),
      fminf(a.p[3], b.p[3]),
    };
  }
  inline static Vec4 max (Vec4 a, Vec4 b) noexcept {
    return {
      fmaxf(a.p[0], b.p[0]),
      fmaxf(a.p[1], b.p[1]),
      fmaxf(a.p[2], b.p[2]),
      fmaxf(a.p[3], b.p[3]),
    };
  }
  inline static Vec4 clamp (Vec4 v, Vec4 min, Vec4 max) noexcept {
    return Vec4::min(Vec4::max(v, min), max);
  }


  template<BlendFunc Func>
  inline static Vec4 blend_func (Vec4 src, Vec4 dst) noexcept;

  template<BlendParam Param>
  inline static Vec4 blend_part (Vec4 src, Vec4 dst, Vec4 val) noexcept;

  template<BlendParam Src = BlendParam::SrcAlpha, BlendParam Dst = BlendParam::OneMinusSrcAlpha, BlendFunc Func = BlendFunc::Add>
  inline static Vec4 blend (Vec4 src, Vec4 dst) noexcept;
};


template<>
inline Vec4 Vec4::unit<0> () noexcept {
  return {1.0F, 0.0F, 0.0F, 0.0F};
}
template<>
inline Vec4 Vec4::unit<1> () noexcept {
  return {0.0F, 1.0F, 0.0F, 0.0F};
}
template<>
inline Vec4 Vec4::unit<2> () noexcept {
  return {0.0F, 0.0F, 1.0F, 0.0F};
}
template<>
inline Vec4 Vec4::unit<3> () noexcept {
  return {0.0F, 0.0F, 0.0F, 1.0F};
}


template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::Add> (Vec4 src, Vec4 dst) noexcept {
  return src + dst;
}
template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::Sub> (Vec4 src, Vec4 dst) noexcept {
  return src - dst;
}
template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::SubR> (Vec4 src, Vec4 dst) noexcept {
  return dst - src;
}
template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::Mul> (Vec4 src, Vec4 dst) noexcept {
  return src * dst;
}
template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::Min> (Vec4 src, Vec4 dst) noexcept {
  return min(src, dst);
}
template<>
inline Vec4 Vec4::blend_func<Vec4::BlendFunc::Max> (Vec4 src, Vec4 dst) noexcept {
  return max(src, dst);
}


template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::Zero> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return zero();
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::One> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val;
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::SrcAlpha> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * src.p[3];
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::DstAlpha> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * dst.p[3];
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::OneMinusSrcAlpha> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * (1.0F - src.p[3]);
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::OneMinusDstAlpha> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * (1.0F - dst.p[3]);
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::SrcColor> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * src;
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::DstColor> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * dst;
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::OneMinusSrcColor> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * (one() - src);
}
template<>
inline Vec4 Vec4::blend_part<Vec4::BlendParam::OneMinusDstColor> (Vec4 src, Vec4 dst, Vec4 val) noexcept {
  return val * (one() - dst);
}


template<Vec4::BlendParam Src, Vec4::BlendParam Dst, Vec4::BlendFunc Func>
inline Vec4 Vec4::blend (Vec4 src, Vec4 dst) noexcept {
  auto s = blend_part<Src>(src, dst, src);
  auto d = blend_part<Src>(src, dst, dst);
  return blend_func<Func>(s, d);
}


inline Vec4 operator* (f32 a, Vec4 b) noexcept {
  return b * a;
}


inline Vec4 operator"" _RGBA (unsigned long long c) noexcept {
  auto i = (u32)c;
  auto s = 1.0F / 255.0F;
  auto r = ((i >> 24) & 0xFF) * s;
  auto g = ((i >> 16) & 0xFF) * s;
  auto b = ((i >> 8) & 0xFF) * s;
  auto a = (i & 0xFF) * s;
  return {r, g, b, a};
}

inline Vec4 operator"" _ARGB (unsigned long long c) noexcept {
  auto i = (u32)c;
  auto s = 1.0F / 255.0F;
  auto a = ((i >> 24) & 0xFF) * s;
  auto g = ((i >> 16) & 0xFF) * s;
  auto b = ((i >> 8) & 0xFF) * s;
  auto r = (i & 0xFF) * s;
  return {r, g, b, a};
}


CLOSE_JLIB_NS
